/******************************************************************************
 * $Id: handlers.c,v 1.118 2004/11/27 00:36:38 jimb Exp $
 * @file
 * @author	Jim Brooks http://www.jimbrooks.org
 * @brief	Event/signal handlers.
 ******************************************************************************/

#include "common.h"
#include "handlers.h"

#include "widgets.h"
#include "find.h"
#include "text.h"
#include "focusbar.h"
#include "menu.h"
#include "main.h"
#include "history.h"
#include "popup.h"
#include "statusbar.h"
#include "file.h"
#include "tags.h"
#include "treefunc.h"

#include "encoding.h"
#include "handlers_module_opened.h"
#include "module_opened.h"

#include <gdk/gdk.h> 
#include <gdk/gdkkeysyms.h>

/*****************************************************************************
 * Handler for the key press event (in main widget).
 * @param	pWidget
 *          Ie pWidgetMain.
 *****************************************************************************/
gboolean
HandlerKeyPressMain( GtkWidget*   pWidget,
                     GdkEventKey* pEvent,
                     gpointer     unused )
{
  /*
   * Quit if startup has not yet created all the widgets.
   */
   if ( ! widgetsReady ) return TRUE;

  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return TRUE;

  /*
   * Remember if the key for selecting the aux text widget was pressed (or released).
   * Remember if the key to suppress tree updates was pressed (or released).
   */
   RecordAuxTextKeyEvent( pEvent );
   RecordTreeKeyEvent(    pEvent );

   if(  pEvent->keyval == GDK_F1 &&
      !(pEvent->state & GDK_CONTROL_MASK || pEvent->state & GDK_SHIFT_MASK))
   {
      if ( pWidgetWithFocus == pWidgetTextActive )
      {
        RunHelper();
        return TRUE;
      }
      else
        return FALSE; // pass to next widget
   }
   /*
    *    F2 => Find Module (file)
    */
   if ( pEvent->keyval == GDK_F2 && !(pEvent->state & GDK_CONTROL_MASK))
   {
       CreateFindDialog( MENU_ITEM_FIND_MODULE );
       return TRUE;
   }
  /*
   * F3 /Find/Text ?
   * or Ctrl+F
   */
   if (    ((pEvent->keyval == GDK_F3 && !(pEvent->state & GDK_CONTROL_MASK))
        || (pEvent->keyval == GDK_f  &&  (pEvent->state & GDK_CONTROL_MASK)) ))
   {
     /*
      * Otherwise, Ctrl+F move cursor to right in text widget,
      * which will disrupt search which depends on cursor position.
      */
      //pEvent->keyval = KLUDGE_IGNORED_GDK_KEY;  /* <-- KLUDGE */

      CreateFindDialog( MENU_ITEM_FIND_TEXT );
      return TRUE;
   }

  /*
   * F4 /Find/Tag ?
   */
   if ( pEvent->keyval == GDK_F4 && !(pEvent->state & GDK_CONTROL_MASK) )
   {
      CreateFindDialog( MENU_ITEM_FIND_TAG );
      return TRUE;
   }

  /*
   * F5 /Goto/Tag ?
   * or Ctrl+Enter
   */
  if ( pEvent->keyval == GDK_F5 ||
        (pEvent->keyval == GDK_Return  &&  (pEvent->state & GDK_CONTROL_MASK)) ||
        (pEvent->keyval == GDK_d       &&  (pEvent->state & GDK_CONTROL_MASK)) ||
        (pEvent->keyval == GDK_D       &&  (pEvent->state & GDK_CONTROL_MASK))  )
   {
      SetSensitivityWhileBusy( FALSE );
      GotoTagUnderCursor();
      SetSensitivityWhileBusy( TRUE );
      // SchemeUpdateFuncTree();
      return TRUE;
   }

  /*
   * F6 Earlier Tag ?
   */
   if ( pEvent->keyval == GDK_F6 && !(pEvent->state & GDK_CONTROL_MASK) )
   {
      HandlerToolbarButtonEarlierTag( NULL, NULL );
      return TRUE;
   }

  /*
   * F7 Later Tag ?
   */
   if ( pEvent->keyval == GDK_F7 && !(pEvent->state & GDK_CONTROL_MASK))
   {
      HandlerToolbarButtonLaterTag( NULL, NULL );
      return TRUE;
   }

  /*
   * F8 Find Matches ?
   */
   if ( pEvent->keyval == GDK_F8 && !(pEvent->state & GDK_CONTROL_MASK))
   {
      SetSensitivityWhileBusy( FALSE );
      FindMatchesUnderCursor();
      SetSensitivityWhileBusy( TRUE );
      return TRUE;
   }

  /*
   * F9 Record Cursor ?
   */
   if ( pEvent->keyval == GDK_F9 && !(pEvent->state & GDK_CONTROL_MASK))
   {
      HandlerToolbarButtonRecordCursor( NULL, NULL );
      return TRUE;
   }

  /*
   * Ctrl+N Find Next (text or tag)
   * This case also appears in key handler of text widget (below)
   * because Ctrl+N is CursorDown in context of text editing.
   */
   if (    (pEvent->keyval == GDK_n  &&  pEvent->state & GDK_CONTROL_MASK)
        || (pEvent->keyval == GDK_g  &&  pEvent->state & GDK_CONTROL_MASK) )
//   if ( pEvent->keyval == GDK_F2 )
   {
     /*
      * The KLUDGE at text widget's key handler text widget must be
      * repeated here.
      */
      gboolean findReverseCopy = findReverse;
      findReverse = FALSE;

      //pEvent->keyval = KLUDGE_IGNORED_GDK_KEY;  /* <-- KLUDGE */
      FindNextTextOrTag();

      findReverse = findReverseCopy;

      return TRUE;
   }

  /*
   * Ctrl+R Reverse Find Next (text or tag)
   */
   if ( pEvent->keyval == GDK_r  &&  pEvent->state & GDK_CONTROL_MASK )
   {
      gboolean findReverseCopy = findReverse;
      findReverse = TRUE;

      FindNextTextOrTag();

      findReverse = findReverseCopy;
      return TRUE;
   }

  /*
   * Sometimes key events for text widget reach this point,
   * but not HandlerKeyPressText() (why?).
   */
   if ( ComplainAboutEditing( pEvent ) ) return TRUE;

  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}

/*****************************************************************************
 * Handler for both key press events in text widget.
 * @param	pWidget
 *          Ie pWidgetTextMain[Aux].
 *****************************************************************************/
gboolean
HandlerKeyPressText( GtkWidget*   pWidget,
                     GdkEventKey* pEvent,
                     gpointer     unused )
{
  /*
   * Quit if startup has not yet created all the widgets.
   */
   if ( ! widgetsReady ) return TRUE;

  /*
   * Ignore if this text widget doesn't have the focus.
   */
   if ( pWidgetWithFocus != pWidgetTextActive )
      return TRUE;

  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return TRUE;

  /*
   * Remember if the key for selecting the aux text widget was pressed (or released).
   * Remember if the key to suppress tree updates was pressed (or released).
   */
#if 0
   RecordAuxTextKeyEvent( pEvent );
   RecordTreeKeyEvent(    pEvent );

  /*
   * Ctrl+N Find Next (text or tag)
   */
   if ( (pEvent->keyval == GDK_n && pEvent->state & GDK_CONTROL_MASK) )
   {
     /*
      * KLUDGE: Even though TRUE is returned to stop propagating this event,
      *         Ctrl+N will be handled by another handler as Cursor Down.
      *         Kludge is to modify event struct to pretend as though
      *         an unhandled key was pressed.
      */
      gboolean findReverseCopy = findReverse;
      findReverse = FALSE;

      pEvent->keyval = KLUDGE_IGNORED_GDK_KEY;  /* <-- KLUDGE */
      FindNextTextOrTag();

      findReverse = findReverseCopy;

      return TRUE;
   }
#endif
  /*
   * Ctrl+L (recenter screen)
   */
   if ( (pEvent->keyval == GDK_l  &&  pEvent->state & GDK_CONTROL_MASK))
   {
      RecenterTextWidget();
      return TRUE;
   }

   if ( ComplainAboutEditing( pEvent ) ) return TRUE;

#if 0
  /*
   * When clicked the word under pWidgetTextMain,
   * then display the tag under cursor in the 
   * pWidgetTextAux
   */
  if ( pWidgetTextActive == pWidgetTextMain )
  {
    static guint id = 0;
    if ( 0 != id )
    {
        if ( !g_source_remove( id ) )
            DebugMsg( "remove idle func %d err", id );
        id = 0;
    }
    id = g_idle_add_full( G_PRIORITY_LOW,
                          cb_RefreshAuxTextWidget,
                          NULL,
                          NULL ); // abc()
  }
#endif
  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}

/*****************************************************************************
 * If a key was pressed in a text widget that would affect it,
 * then complain and return TRUE.
 *****************************************************************************/
gboolean
ComplainAboutEditing( GdkEventKey* pEvent )
{
	/************************
	 * support edit currently ,
	 * so this func do nothing.
	 * **********************/
	return FALSE;
#if 0 // do nothing
  /*
   * Do nothing if text widget doesn't have focus.
   */
   if ( pWidgetWithFocus != pWidgetTextActive )
      return FALSE;

   if (    pEvent->state & GDK_CONTROL_MASK
        || pEvent->state & GDK_MOD1_MASK )
   {
     /*
      * Fall-thru to complain if Ctrl+D(elete).
      */
      if ( pEvent->keyval != GDK_d )
         return FALSE;
   }

  /*
   * Complain if user is trying to edit a file (which can't be saved).
   */
   switch ( pEvent->keyval )
   {
     /*
      * Allow cursor movement and other passive keys.
      */
      case GDK_Up:			case GDK_Down:
      case GDK_Left:		case GDK_Right:
      case GDK_Page_Up:		case GDK_Page_Down:
      case GDK_Home:		case GDK_End:
      case GDK_Alt_L:		case GDK_Alt_R:
      case GDK_Shift_L:		case GDK_Shift_R:
      case GDK_Control_L:	case GDK_Control_R:
      case GDK_Meta_L:		case GDK_Meta_R:
      case GDK_F1:
      case GDK_F2:
      case GDK_F3:
      case GDK_F4:
      case GDK_F5:
      case GDK_F6:
      case GDK_F7:
      case GDK_F8:
      case GDK_F9:
      case GDK_F10:
      case GDK_F11:
      case GDK_F12:
      case GDK_Escape:
      case GDK_Caps_Lock:
      case GDK_Num_Lock:
         break;

     /*
      * Complain once.
      */
      default:
      {
         static gboolean shownBefore = FALSE;

         if ( ! shownBefore )
         {
            Popup( "Editing files isn't supported!",
                   "Warning",
                   FONT_PROPORTIONAL );
            shownBefore = TRUE;
         }

        /*
         * Modify the key event struct to cause the keystroke to be ignored.
         */
         pEvent->type   = GDK_NOTHING;
         pEvent->keyval = 0;

         return TRUE;
      }
   }

   return FALSE;
#endif
}

/*****************************************************************************
 * Remember if the key for selecting the aux text widget was pressed (or released).
 *****************************************************************************/
void
RecordAuxTextKeyEvent( GdkEventKey* pEvent )
{
g_return_if_fail( pEvent );

   if ( pEvent->keyval == auxKeyGdkKeyval )
      auxKeyPressed = TRUE;
   else if ( !(pEvent->state & auxKeyGdkMask) ) /* this is/was needed, see CVS log */
      auxKeyPressed = FALSE;
}

/*****************************************************************************
 * Remember if the key to suppress tree updates was pressed (or released).
 *****************************************************************************/
void
RecordTreeKeyEvent( GdkEventKey* pEvent )
{
g_return_if_fail( pEvent );

   if ( pEvent->keyval == treeKeyGdkKeyval )
      treeKeyPressed = TRUE;
   else if ( !(pEvent->state & treeKeyGdkMask) ) /* this is/was needed, see CVS log */
      treeKeyPressed = FALSE;
}

/*****************************************************************************
 * Handler for key press events in clist widgets (of notebook).
 * @param	pWidget
 *****************************************************************************/
gboolean
HandlerKeyPressClist( GtkWidget*   pWidget,
                      GdkEventKey* pEvent,
                      gpointer     unused )
{
   g_return_val_if_fail(    pWidget == pWidgetClistTags
                         || pWidget == pWidgetClistFound
                         || pWidget == pWidgetClistModules
                         || pWidget == pWidgetClistHistory, FALSE );

  /*
   * Remember if the key for selecting the aux text widget was pressed (or released).
   * Remember if the key to suppress tree updates was pressed (or released).
   */
   RecordAuxTextKeyEvent( pEvent );
   RecordTreeKeyEvent(    pEvent );

  /*
   * Clear the Ctrl[Shift] bit in the key event struct,
   * otherwise the key will cause undesirable behavior
   * in addition to preventing Ctrl[Shift] + Space
   * updating the aux. text widget.
   */
   if ( pEvent->state & GDK_CONTROL_MASK )
   {
     /*
      * But let Ctrl+Home[End] work normally.
      */
      if (    pEvent->keyval != GDK_Home
           && pEvent->keyval != GDK_End )
         pEvent->state &= ~GDK_CONTROL_MASK;
   }
   if ( pEvent->state & GDK_SHIFT_MASK )
   {
     /*
      * But let Ctrl+Home[End] work normally.
      */
      if (    pEvent->keyval != GDK_Home
           && pEvent->keyval != GDK_End )
         pEvent->state &= ~GDK_SHIFT_MASK;
   }

  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}


/***************************************************
 不将焦点设置到 pView, 而往里面加载模块
 ***************************************************/
void
LoadTextWidgetWithTag(const tag_t * tag,
                                           GtkWidget * pView,
                                           gdouble vertFrac)
{
    if ( NULL == tag || NULL == pView )
        return;
    gsize fSize=0;
    gsize line=0;
    // 不能用下边的这种方法
    //gchar * buffer = ModuleBuffer( tag->pModuleName, NULL);
    gchar * buffer = LoadFile(tag->pModuleName, &fSize);
    if ( ! g_utf8_validate( buffer, -1, NULL ) )
    {
        gsize readSize=0, writeSize=0;
        gchar * newBuffer=NULL;
        newBuffer = g_convert(buffer,
                                                fSize,
                                                "UTF-8",
                                                detect_charset(buffer),
                                                &readSize,
                                                &writeSize,
                                                NULL);
        if ( NULL != newBuffer && readSize == fSize )
        {
            g_free(buffer);
            buffer = newBuffer;
            fSize = writeSize;
        }
        else
        {
            // convert charset error.
            g_free(buffer);
            g_free(newBuffer);
            return;
        }
    }// g_utf8_validate
    
    GtkTextBuffer * pTextBuffer = gtk_text_view_get_buffer(
                              GTK_TEXT_VIEW(pView));
    gtk_source_buffer_begin_not_undoable_action(GTK_SOURCE_BUFFER(pTextBuffer));

    gtk_text_buffer_set_text(pTextBuffer, buffer, fSize);
    g_free(buffer);
    if ( hilite )
        SetBufferHighlight( GTK_SOURCE_BUFFER(pTextBuffer),
                                          tag->pModuleName);

    gtk_text_buffer_set_modified(pTextBuffer, FALSE);
    
    gtk_source_buffer_end_not_undoable_action(GTK_SOURCE_BUFFER(pTextBuffer));

    //pWidgetTextActive = pWidgetTextAux;
    //ScrollTextWidgetToLine(found_tag->line, 0.25);
    
    line = tag->lineInt;
    if ( line <= 0 ) line = 1;

   GtkTextIter iter;
   gtk_text_buffer_get_iter_at_line(pTextBuffer, &iter, line );
   gtk_text_buffer_place_cursor(pTextBuffer, &iter);
   ScrollToCursor(GTK_TEXT_VIEW(pView), vertFrac);
   return;
}


/*****************************************************************************
 * Shared handler for key release events.
 * @param	pWidget
 *          Ie pWidgetTextMain[Aux].
 *****************************************************************************/
gboolean
HandlerKeyRelease( GtkWidget*   pWidget,
                   GdkEventKey* pEvent,
                   gpointer     unused )
{
  /*
   * Clear a flag if the key for selecting the aux text widget was released.
   */
   if (     (pEvent->keyval == auxKeyGdkKeyval)
        || !(pEvent->state & auxKeyGdkMask) ) /* perhaps redundant, perhaps not */
      auxKeyPressed = FALSE;

  /*
   * Clear a flag if the key for disabling tree updates was released.
   */
   if (     (pEvent->keyval == treeKeyGdkKeyval)
        || !(pEvent->state & treeKeyGdkMask) ) /* perhaps redundant, perhaps not */
      treeKeyPressed = FALSE;

  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}

void
SearchAndShowTagUnderCursorToAux(void)
{
	GString * pGstr=NULL;
	gsize textLen;
	gchar * pText;

    gint cursorIdx = OffsetAtCursor();

   pText = ActiveTextWidgetContents(&textLen);	//ModuleBuffer();
   pGstr = ScoopWordAtCursor( MAX_TAG_NAME_LEN,
													pText,
													textLen,
													cursorIdx);
   if ( NULL == pGstr )
   {
       PrintStatusbar(_("No word under cursor."));
       goto quit;
    }
    else
    {
        // 
        tag_t * found_tag = FindTag( pGstr->str, NULL);
        if ( NULL == found_tag )
        {
             goto quit;
         }
         else
         {
             // GetWidgetTextAux() / GetModuleTextAux()
             LoadTextWidgetWithTag(found_tag,  GetWidgetTextAux(), 0.25);
             // ScrollTextWidgetToLine(found_tag->line, 0.25);
             goto quit;
         }
    }

quit:
    if ( pGstr )
        g_string_free(pGstr, TRUE);
    return;
}// searchAnd...

/****************************************************************************
 * Callback for button-press-event.
 * **************************************************************************/
gboolean
cb_button_press_event(GtkWidget *view,
					  GdkEventButton *event,
					  gpointer data)
{
	static gssize count=0;
#ifdef HYPERSRC_DEBUG	
	g_print("Press button %d times.\n", ++count);
#endif	
   if ( IsAnyMajorFunctionBusy(TRUE) ) return FALSE;

  /*
   * When clicked the word under pWidgetTextMain,
   * then display the tag under cursor in the 
   * pWidgetTextAux , 3 = the right button of mouse
   */
  if ( event->button != 3 && pWidgetTextActive == pWidgetTextMain )
  {
    static guint id = 0;
    if ( 0 != id )
    {
        if ( !g_source_remove( id ) )
            DebugMsg( "remove idle func %d err", id );
        id = 0;
    }
    id = g_idle_add_full( G_PRIORITY_LOW,
                          cb_RefreshAuxTextWidget,
                          &id,
                          NULL ); // abc()
  }

//   SetSensitivityWhileBusy( FALSE );

//   SearchAndShowTagUnderCursorToAux();

//   SetSensitivityWhileBusy( TRUE );

   // first: get the word under cursor
   // second: find the word in pGlistTags
   // third: if find == true; then load the file into the TextAux, and scroll to the word
   //           else if find == false, do nothing
   // GString pGstr = ScoopWordAtCursor();
    
	/*
	 * Return FALSE to defer event to next handler
	 */

	return FALSE;
}

/*****************************************************************************
 * Handler for the key press event (in a Find dialog widget).
 * @param	pWidget
 *          Ie pWidgetDialogFind.
 *****************************************************************************/
gboolean
HandlerKeyPressDialogFind( GtkWidget*   pWidget,
                           GdkEventKey* pEvent,
                           gpointer     unused )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return TRUE;

  /*
   * ESC ?
   */
   if ( pEvent->keyval == GDK_Escape )
   {
     /*
      * If a dialog is open then close it.
      */
      if ( pWidgetDialogFind )
         gtk_widget_destroy( pWidgetDialogFind );
      return TRUE;
   }

  /*
   * Enter ?
   */
   if ( pEvent->keyval == GDK_Return  ||  pEvent->keyval == GDK_KP_Enter )
   {
      if ( pEvent->keyval == GDK_KP_Enter ) /* KLUDGE */
         pEvent->keyval = GDK_Return;

      FindDialogClickedFind();

      return TRUE;
   }

  /*
   * Alt+F ? (select "_Find" button in Find dialog)
   */
   if ( (pEvent->keyval == GDK_f && pEvent->state & GDK_MOD1_MASK) )
   {
      FindDialogClickedFind();
      return TRUE;
   }

  /*
   * Alt+W ? (select "_Word at Cursor" button in Find dialog)
   * Ignore if button wasn't created.
   */
   if (    (pEvent->keyval == GDK_w && pEvent->state & GDK_MOD1_MASK)
        && buttonWordAtCursorFlag )
   {
      WordAtCursorClicked();
      return TRUE;
   }

  /*
   * Alt+P ? (select "Restore Cursor _Position" button in Find dialog)
   * Ignore if button wasn't created.
   */
   if (    (pEvent->keyval == GDK_p && pEvent->state & GDK_MOD1_MASK)
        && buttonRestoreCursorFlag )
   {
      RestoreCursorClicked();
      return TRUE;
   }

  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}

/*****************************************************************************
 * Handler for the key press event in line # dialog widget.
 * @param	pWidget
 *          Ie pWidgetDialogLine.
 *****************************************************************************/
gboolean
HandlerKeyPressDialogLine( GtkWidget*   pWidget,
                           GdkEventKey* pEvent,
                           gpointer     unused )
{
  /*
   * ESC ?
   */
   if ( pEvent->keyval == GDK_Escape )
   {
     /*
      * Close dialog.
      */
      gtk_widget_destroy( pWidget );
      return TRUE;
   }

  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}

#if 0 //lv //not needed again

/*****************************************************************************
 * Handler for when the goto line combo widget is activated (ie Enter pressed).
 * @param	pWidget
 *          Pointer to text entry widget of /Goto/Line dialog.
 *****************************************************************************/
gboolean
HandlerActivateComboLine( GtkWidget*   pWidget,
                          GdkEventKey* pEvent,
                          gpointer     unused )
{
   int	 line;
   char* pEntryText;

  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return TRUE;

  /*
   * The line # the user wishes to goto is in the entry widget.
   * Convert the text string to a binary number.
   */
   pEntryText = gtk_editable_get_chars( GTK_EDITABLE(GTK_ENTRY(GTK_COMBO(pWidgetComboLine)->entry)),
                                        0, -1 );

   line = atoi( (const char*)pEntryText ) - 1;

  /*
   * Ensure the line # is valid.
   */
   if ( line >= 0  &&  line < MAX_LINES )
   {
     /*
      * Scroll text widget to requested line #.
      */
      if ( HighlightLine( line ) )
         ScrollTextWidgetToLine( line, SCROLL_TEXT_VERT_FRAC );
      else
         PrintStatusbar( "line # is invalid" );

     /*
      * Record the string the user typed.
      */
      PrependComboDropDown( pEntryText,
                            &pGlistLineStrings );
   }
   else
      PrintStatusbar( "Requested line # is invalid." );

  /*
   * Close dialog.
   */
   gtk_widget_destroy( pWidgetDialogLine );

   g_free( pEntryText );

  /*
   * Event was handled here, return TRUE.
   */
   return TRUE;
}

#endif

gint
abc( gpointer data)
{
    static gint i = 0;
  /*
   * Returning zero causes GTK+ to stop calling this idle func.
   *
   * Is the text widget empty?
   */
   if ( IsTextWidgetEmpty() ) return FALSE;
   
  /*
   * Update functree if necessary.
   */
   if ( IsAnyMajorFunctionBusy(0)  || IsAnyTreeFunctionBusy() )
        return FALSE;
   if ( i == 4 )
   {
        i=0;    
        return FALSE;
   }
   else
   {
        i++;
        g_print("Idles func call\n");
        return TRUE;
   }
}

/*****************************************************************************
 * Handler for updating the line #.
 * Notes: The reason that an idle function prints the line # is to allow
 *        GTK+ to finish updating the text widget, esp. when cursor keys
 *        are pressed.  This is why the key event handler LineNumHandler()
 *        enables and defers to this idle function.
 *****************************************************************************/
gboolean
HandlerLineNum( GtkWidget*      pWidget,
                GdkEventButton* pEvent,
                gpointer        ununsed )
{
   if ( ! widgetsReady ) return FALSE;

  /*
   * Enable an idle function to be called after GTK+ becomes idle,
   * which is the correct time to accurately update the line #.
   */
   gtk_idle_add( IdleLineNum,
                 NULL );


  /*
   * Return FALSE to defer event to next handler.
   */
   return FALSE;
}

/*****************************************************************************
 * Handler for the button "Earlier Tag".
 *****************************************************************************/
void
HandlerToolbarButtonEarlierTag( GtkWidget* pWidgetUnused,
                                gpointer   unused )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;

   SetSensitivityWhileBusy( FALSE );
   StepHistoryList( 1 );
   SetSensitivityWhileBusy( TRUE );
}

/*****************************************************************************
 * Handler for the button "Later Tag".
 *****************************************************************************/
void
HandlerToolbarButtonLaterTag( GtkWidget* pWidgetUnused,
                              gpointer   unused )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;

   SetSensitivityWhileBusy( FALSE );
   StepHistoryList( 0 );
   SetSensitivityWhileBusy( TRUE );
}

/*****************************************************************************
 * Handler for the button "Goto Tag".
 *****************************************************************************/
void
HandlerToolbarButtonGotoTag( GtkWidget* pWidgetUnused,
                             gpointer   unused )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;

   SetSensitivityWhileBusy( FALSE );
   GotoTagUnderCursor();
   SetSensitivityWhileBusy( TRUE );
   ScheduleUpdateFuncTree();
}

/*****************************************************************************
 * Handler for the button "Find Matches".
 *****************************************************************************/
void
HandlerToolbarButtonFindMatches( GtkWidget* pWidgetUnused,
                                 gpointer   unused )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;

   SetSensitivityWhileBusy( FALSE );
   FindMatchesUnderCursor();
   SetSensitivityWhileBusy( TRUE );
}

/*****************************************************************************
 * Handler for the button "Record Position".
 *****************************************************************************/
void
HandlerToolbarButtonRecordCursor( GtkWidget* pWidgetUnused,
                                  gpointer   unused )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;

  /*
   * Is the text widget empty?
   */
   if ( IsTextWidgetEmpty() )
   {
      PrintStatusbar( "Text widget is empty." );
      return;
   }

  /*
   * Record cursor in tag history.
   */
   RecordCursorInTagHistory( "(user-recorded position)" );

   PrintStatusbar( "Recorded cursor position in history." );

   return;
}

/*****************************************************************************
 * Handler for when another notebook page is switched to.
 *
 * HandlerSwitchPageNotebook() and HandlerDrawClistKludge() are part of a kludge,
 * see comments at HandlerDrawClistKludge().
 *****************************************************************************/
void
HandlerSwitchPageNotebook( GtkNotebook*     pNotebook,
                           GtkNotebookPage* pNotebookPage,
                           gint             page_num,
                           gpointer         unused )
{
  /*
   * KLUDGE: Set a flag for tags/modules clist "draw" handler.
   */
   kludgeNotebookSwitchPage = TRUE;

  /*
   * Return void (yes, not FALSE).
   */
   return;
}

/*****************************************************************************
 * KLUDGE:
 * Handler for "draw" signal of tags/modules clists.
 * A known problem is when the notebook page is switched to a tags/modules clist,
 * then sometimes the clists fails to scroll to the selected row, causing
 * the selected row to be outside of the visible area.
 * Kludge is to call gtk_clist_moveto() when the clist receives a "draw" signal
 * just after its notebook page is switched to.
 *****************************************************************************/
void
HandlerDrawClistKludge( GtkWidget*    pWidget,
                        GdkRectangle* pRect,
                        gpointer      unused )
{
   gint row;

  /*
   * (Yes, void will be returned.)
   *
   * If a notebook page is not being switched then don't bother,
   * else clear notebook-page-switch flag and continue.
   */
   if ( kludgeNotebookSwitchPage )
      kludgeNotebookSwitchPage = FALSE;
   else
      return;

  /*
   * Determine which selected-row var to use.
   */
   if ( pWidget == pWidgetClistTags )
      row = widgetTagsClistSelectedRow;
   else if ( pWidget == pWidgetClistModules )
      row = widgetModulesClistSelectedRow;
   else
      return; /* shouldn't happen */

  /*
   * Skip if no row is selected.
   */
   if ( row == ROW_NONE )
      return;

  /*
   * This trick should cause the selected row to appear in the clist scrolled widget.
   */
   gtk_clist_moveto( GTK_CLIST(pWidget),
                     row,
                     0,
                     0.5,
                     0 );

  /*
   * Return void (yes, not FALSE).
   */
   return;
}

/*****************************************************************************
 * Install handlers for when a widget gains/lose focus.
 *****************************************************************************/
void
InstallFocusHandlers( GtkWidget* pWidget )
{
   g_signal_connect( G_OBJECT(pWidget),
                       "focus-in-event",
                       G_CALLBACK(HandlerFocusIn),
                       NULL );
   g_signal_connect( G_OBJECT(pWidget),
                       "focus-out-event",
                       G_CALLBACK(HandlerFocusOut),
                       NULL );
}

/*****************************************************************************
 * Handler for when a widget gains focus.
 *****************************************************************************/
gboolean
HandlerFocusIn( GtkWidget*     pWidget,
                GdkEventFocus* pEvent,
                gpointer       unused )
{
  /*
   * Record which widget has the focus.
   */
   pWidgetWithFocus = pWidget;

   if ( pWidget == pWidgetTextMain )
   {
     /*
      * FocusbarFocusLose() is called because otherwise sometimes when
      * the main widget gains focus, both focusbars will incorrectly indicate focus.
      */
      FocusbarFocusGain( pFocusbarTextMain );
      FocusbarFocusLose( pFocusbarTextAux );
      SetActiveTextWidget(   pWidget );
      GiveFocusToTextWidget( pWidget );
   }
   else if ( pWidget == pWidgetTextAux )
   {
     /*
      * FocusbarFocusLose() is called because otherwise sometimes when
      * the main widget gains focus, both focusbars will incorrectly indicate focus.
      */
      FocusbarFocusGain( pFocusbarTextAux );
      FocusbarFocusLose( pFocusbarTextMain );
      SetActiveTextWidget(   pWidget );
      GiveFocusToTextWidget( pWidget );
   }
   else if ( pWidget == pWidgetMain )
   {
     /*
      * Main widget has the focus, so repaint the focusbar that had the focus.
      */
      if      ( pFocusbarTextMain  &&  pWidgetTextActive == pWidgetTextMain )
         FocusbarFocusGain( pFocusbarTextMain );
      else if ( pFocusbarTextAux   &&  pWidgetTextActive == pWidgetTextAux )
         FocusbarFocusGain( pFocusbarTextAux );
   }
   else
   {
     /*
      * When a non-text widget gains the focus, that should be indicated by
      * focusbars over the text widgets.
      */
      if ( !keepFocusbarLit )
      {
         if ( pFocusbarTextMain ) FocusbarFocusLose( pFocusbarTextMain );
         if ( pFocusbarTextAux  ) FocusbarFocusLose( pFocusbarTextAux );
      }
   }

   /*
   * Continue to next handler.
   */
   return FALSE;
}

/*****************************************************************************
 * Handler for when a widget loses focus.
 * This is called after the new widget gains focus.
 *****************************************************************************/
gboolean
HandlerFocusOut( GtkWidget*     pWidget,
                 GdkEventFocus* pEvent,
                 gpointer       unused )
{
   if ( pWidget == pWidgetTextMain )
   {
      if ( !keepFocusbarLit) FocusbarFocusLose( pFocusbarTextMain );
   }
   else if ( pWidget == pWidgetTextAux )
   {
      if ( !keepFocusbarLit) FocusbarFocusLose( pFocusbarTextAux );
   }
   else if ( pWidget == pWidgetMain )
   {
     /*
      * No focusbar should be lit when the main widget loses the focus
      * (the user has switched to another app).
      */
      if ( pFocusbarTextMain ) FocusbarFocusLose( pFocusbarTextMain );
      if ( pFocusbarTextAux  ) FocusbarFocusLose( pFocusbarTextAux );

     /*
      * If a user switches to another app while still pressing Ctrl/Shift,
      * then this program will miss the release event.  When the user switches back,
      * an annoyance is that function tree will stubbornly stop updating and/or
      * the aux text widget stubbornly remains active (Ctrl/Shift are effectively stuck).
      * Therefore, clear these vars as though Ctrl/Shift are released, regardless.
      * Of course, if the user switches back while still pressing Ctrl/Shift,
      * the program will think both were released.  But this other quirk
      * is easily solved when the user tries re-pressing Ctrl/Shift.
      */
//      auxKeyPressed = FALSE;
      treeKeyPressed = FALSE;
   }

  /*
   * Continue to next handler.
   */
   return FALSE;
}

/*****************************************************************************
 * Handler for when a focusbar (which is over a text widget) is clicked.
 *****************************************************************************/
gboolean
HandlerFocusbarButtonEvent( GtkWidget*      pWidget,
                            GdkEventButton* pEvent,
                            gpointer        pFocusbar )
{
g_return_val_if_fail( pWidget  &&  pFocusbarTextMain  &&  pFocusbarTextAux, TRUE );

   if ( pWidget == pFocusbarTextMain->pWidget )
   {
      SetActiveTextWidget(   pWidgetTextMain );
      GiveFocusToTextWidget( pWidgetTextMain );
      FocusbarFocusLose( pFocusbarTextAux );
      FocusbarFocusGain( (focusbar_t*) pFocusbar );
   }
   else if ( pWidget == pFocusbarTextAux->pWidget )
   {
      SetActiveTextWidget(   pWidgetTextAux );
      GiveFocusToTextWidget( pWidgetTextAux );
      FocusbarFocusLose( pFocusbarTextMain );
      FocusbarFocusGain( (focusbar_t*) pFocusbar );
   }

   return TRUE;
}

/*****************************************************************************
 * Handler for button widgets.
 *****************************************************************************/
void
HandlerButtons( GtkWidget* pWidget,
                gpointer   pUnused )
{
  /*
   * Which button widget?
   */
   if ( pWidget == pWidgetCheckButtonFunctree )
      disableFuncTreeUpdates = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(pWidget) );
   else
      WarningMalfunction();
}

/*****************************************************************************
 * Handler for mouse events (mouse wheel).
 * @remarks Mouse wheel scrolls more/faster if Left-Ctrl (tree key) is pressed.
 * Written by Linus Gasser, slightly modified by Jim Brooks.
 *****************************************************************************/
gboolean
HandlerMouseButton( GtkWidget*      widget,
                    GdkEventButton* event,
                    gpointer        user_data )
{
   GtkAdjustment* adj = (GtkAdjustment*)user_data;
   switch( event->button )
   {
     /*
      * button #4 is used when scrolling up
      * fast scrolling when Left-Ctrl is pressed
      * (standard behaviour on KDE)
      */
      case 4:
      if ( treeKeyPressed )
         gtk_adjustment_set_value( adj, adj->value - adj->page_increment * mouseWheelFast );
      else
         gtk_adjustment_set_value( adj, adj->value - adj->page_increment * mouseWheelNormal );
      return TRUE;
      break;

     /*
      * button #5 is used when scrolling down
      * fast scrolling when Left-Ctrl is pressed
      * (standard behaviour on KDE)
      */
      case 5:
      if ( treeKeyPressed )
         gtk_adjustment_set_value( adj, adj->value + adj->page_increment * mouseWheelFast );
      else
         gtk_adjustment_set_value( adj, adj->value + adj->page_increment * mouseWheelNormal );
      return TRUE;
      break;

      default:
      return FALSE;
      break;
   }
}


/*****************************************************************************
 * In order to gracefully exit if stopped by user during startup.
 *****************************************************************************/
gint
HandlerDeleteEvent( GtkWidget* pWidget,
                    GdkEvent*  pEvent,
                    gpointer   pUnused )
{
   if ( pWidget == pWidgetMain )
   {
      if ( IsMainModuleModified() )
	 {
		//Save changes to `file'?
		// No, Cancel, Yes
		gint result = AskSaveOrNot();
		if ( result == GTK_RESPONSE_YES || 
		      result == GTK_RESPONSE_NO )
		{
		    // do nothing
		}
		else
		      return TRUE;
	 }// if 2
   }//if 1
   
   SaveVars();
   gtk_exit(0);

   return TRUE; /* unexpected */
}

/*****************************************************************************
 * User clicked the "Word At Cursor" button in a Find/Text dialog.
 *****************************************************************************/
void
WordAtCursorClicked( void )
{
   GString*		pGstr		= NULL;
   gchar*		pText		= NULL;
   guint		textLen;
   guint		cursorIdx;

  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;
   if ( IsTextWidgetEmpty() ) return;
  /*
   * Obtain contents of text widget.
   */
   pText = ActiveTextWidgetContents( &textLen );
   if ( pText == NULL  ||  textLen > MAX_TEXT_BUFFER_LEN ) goto error;

  /*
   * Get index of cursor.
   */
   cursorIdx = OffsetAtCursor();//TextCursorIdx();
   if ( cursorIdx < 0  ||  cursorIdx > textLen ) goto error;

  /*
   * Scoop word at cursor.
   * Return if cursor is not over a word.
   */

   pGstr = ScoopWordAtCursor( MAX_WORD_LEN,
                              pText,
                              textLen,
                              cursorIdx );
   if ( pGstr == NULL ) goto quit;		/* benignly quit if cursor isn't on a word */

  /*
   * Insert scooped word into text entry.
   */
   gtk_entry_set_text( GTK_ENTRY( GTK_COMBO(pWidgetComboFind)->entry ),
                       pGstr->str );

  /*
   * Free mem, return.
   */
quit:
   if ( pGstr ) g_string_free( pGstr, TRUE );
   return;

  /*
   * Oops.
   */
error:
   WarningMalfunction();
   return;
}

/*****************************************************************************
 * User clicked the "Restore Cursor Position" button in a Find dialog.
 *****************************************************************************/
void
RestoreCursorClicked( void )
{
  /*
   * Prevent nesting major functions.
   */
   if ( IsAnyMajorFunctionBusy(TRUE) ) return;

g_return_if_fail( buttonRestoreCursorFlag );

  /*
   * Restore state of cursor and text widget before user did a search.
   */
   RestoreCursor( &cursorText );

  /*
   * Close find dialog.
   */
   FindDialogClose();

   return;
}
